package com.punch.common.simple.redis.utils;


import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

@Component
public class RedisUtil<T> {

    /**
     * 默认过期时长，单位：秒/ 三十分钟
     */
    public final static long DEFAULT_EXPIRE = 180 * 10;
    /**
     * 不设置过期时长
     */
    public final static long NOT_EXPIRE = -1;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private HashOperations<String, String, Object> hashOperations;
    @Autowired
    private ListOperations<String, Object> listOperations;

    public static <T> Predicate<T> distinctByValue(Function<? super T, ?> keyExtractor) {
        ConcurrentHashMap<Object, Boolean> map = new ConcurrentHashMap<>(1024);
        return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
    }

    public void setValue(String key, T value) {
        redisTemplate.opsForValue().set(key, value);
    }

    public void setValue(String key, T value, Long time) {
        setValue(key, value);
        redisTemplate.expire(key, time, TimeUnit.SECONDS);
    }

    public void setValue(String key, T value, Long time, TimeUnit unit) {
        setValue(key, value);
        redisTemplate.expire(key, time, unit);
    }

    public T getValue(String key) {
        ValueOperations<String, T> valueOperations = redisTemplate.opsForValue();
        return valueOperations.get(key);
    }

    public void deleteValue(String key) {
        redisTemplate.delete(key);
    }

    public boolean exists(String key) {
        if (getValue(key) == null) {
            return false;
        } else {
            return true;
        }
    }

    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 重复提交
     *
     * @param key
     * @return
     */
    public long repeatSubmit(String key) {
        ValueOperations<String, Integer> valueOperations = redisTemplate.opsForValue();
        long ret = valueOperations.increment(key, 1);
        redisTemplate.expire(key, 10L, TimeUnit.SECONDS);
        return ret;
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -1代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 删除hash值
     *
     * @param key      key
     * @param hashKeys hashKeys
     */
    public void delHash(String key, String... hashKeys) {
        Arrays.stream(hashKeys).forEach(hashKey -> hashOperations.delete(key, hashKeys));
    }

    /**
     * 先根据key删除
     * 然后在把list放入缓存中
     *
     * @param key                key
     * @param getHashKeyFunction 获取hashKey值的方法
     * @param list               list实体类
     */
    public <T> void putHashList(String key, Function<T, String> getHashKeyFunction, List<T> list, Long expire) {
        putHashList(key, getHashKeyFunction, list);
        if (expire != NOT_EXPIRE) {
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
    }

    public <T> void putHashList(String key, Function<T, String> getHashKeyFunction, List<T> list) {
        deleteValue(key);
        list.stream().filter(distinctByValue(getHashKeyFunction)).forEach(model -> putHashModel(key, getHashKeyFunction, model));
    }

    /**
     * 将实体类放到Hash里面
     *
     * @param key                key
     * @param getHashKeyFunction 获取hashKey值的方法
     * @param model              实体类
     * @param <T>                T
     */
    public <T> void putHashModel(String key, Function<T, String> getHashKeyFunction, T model, Long expire) {
        putHashModel(key, getHashKeyFunction, model);
        if (expire != NOT_EXPIRE) {
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
    }

    public <T> void putHashModel(String key, Function<T, String> getHashKeyFunction, T model) {
        hashOperations.put(key, getHashKeyFunction.apply(model), JSONObject.toJSON(model).toString());
    }

    /**
     * 根据 key 获取List
     *
     * @param key   key
     * @param clazz clazz
     * @param <T>   T
     * @return List<T>
     */
    public <T> List<T> getHashList(String key, Class<T> clazz, Long expire) {
        if (expire != NOT_EXPIRE) {
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
        return getHashList(key, clazz);
    }

    public <T> List<T> getHashList(String key, Class<T> clazz) {
        Optional<List<Object>> objectList = Optional.ofNullable(hashOperations.values(key));
        return objectList.map(objects -> objects.stream()
                .map(t -> JSONObject.parseObject(JSONObject.toJSON(t).toString(), clazz))
                .collect(Collectors.toList())).orElse(null);
    }

    /**
     * 根据 key 和 hashKey 获取值
     *
     * @param key     key
     * @param hashKey hashKey
     * @param clazz   clazz
     * @param <T>     T
     * @return T
     */
    public <T> T getHashObject(String key, String hashKey, Class<T> clazz) {
        Optional<Object> object = Optional.ofNullable(hashOperations.get(key, hashKey));
        if (object.isPresent()) {
            return JSONObject.parseObject(JSONObject.toJSON(object).toString(), clazz);
        }
        return null;
    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            return false;
        }
    }


    /**
     * 获取全部list
     *
     * @param key    key
     * @param clazz  clazz
     * @param <T>    T
     * @param expire 超时时间
     * @return return
     */
    public <T> List<T> getList(String key, Class<T> clazz, Long expire) {
        if (expire != NOT_EXPIRE) {
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
        return getList(key, clazz);
    }

    public <T> List<T> getList(String key, Class<T> clazz) {
        Optional<List<Object>> list = Optional.ofNullable(listOperations.range(key, 0, listOperations.size(key)));
        return list.map(objects -> objects.stream()
                .map(t -> JSONObject.parseObject(JSONObject.toJSON(t).toString(), clazz))
                .collect(Collectors.toList())).orElse(null);
    }

    /**
     * 先根据key删除
     * 然后在把list放入缓存中
     *
     * @param key              key
     * @param getValueFunction 获取value值的方法
     * @param list             list
     * @param <T>              T
     * @param expire           超时时间
     */
    public <T> void leftPushList(String key, List<T> list, Function<T, String> getValueFunction, Long expire) {
        leftPushList(key, list, getValueFunction);
        if (expire != NOT_EXPIRE) {
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
    }

    public <T> void leftPushList(String key, List<T> list, Function<T, String> getValueFunction) {
        deleteValue(key);
        list.stream().filter(distinctByValue(getValueFunction)).forEach(model -> leftPushModel(key, model, getValueFunction));
    }

    public <T> void leftPushModel(String key, T model, Function<T, String> getValueFunction) {
        listOperations.leftPush(key, getValueFunction.apply(model));
    }
}
